package com.jijs.main;

import java.net.URLEncoder;

import org.jsoup.select.Elements;
import org.jsoup.nodes.Element;

import com.jijs.model.CrawlDatum;
import com.jijs.model.CrawlDatums;
import com.jijs.model.Page;
import com.jijs.plugin.ram.RamCrawler;

/**
 * 
 * 本教程演示了WebCollector 2.20的新特性。
 * 
 * 下载 WebCollector最新jar包可在WebCollector github主页下载。
 * 
 * MetaData: MetaData是每个爬取任务的附加信息,灵活应用MetaData可以大大简化爬虫的设计。
 * 例如Post请求往往需要包含参数，而传统爬虫单纯使用URL来保存参数的方法不适合复杂的POST请求。
 * 
 * 一些爬取任务希望获取遍历树的深度信息，这也可以通过MetaData轻松实现， 可参见教程DemoDepthCrawler。
 * 
 * RamCrawler: RamCrawler不需要依赖文件系统或数据库，适合一次性的爬取任务。 如果希望编写长期任务，请使用BreadthCrawler。
 * 
 * 本教程实现了一个爬取Bing搜索前n页结果的爬虫，爬虫的结果直接输出到标准输出流
 * 如果希望将爬取结果输出到ArrayList等数据结构中，在类中定义一个ArrayList的成员变量，
 * 输出时将结果插入ArrayList即可，这里需要注意的是爬虫是多线程的，而ArrayList不是线程
 * 安全的，因此在执行插入操作时，可使用下面的方式上锁保证安全。
 * 
 * 本教程中对Bing搜索的解析规则可能会随Bing搜索的改版而失效。
 * 
 * @author jijs
 */
public class DemoBingCrawler extends RamCrawler {

	public DemoBingCrawler(String keyword, int maxPageNum) throws Exception {
		for (int pageNum = 1; pageNum <= maxPageNum; pageNum++) {
			String url = createBingUrl(keyword, pageNum);
			CrawlDatum datum = new CrawlDatum(url).meta("keyword", keyword)
					.meta("pageNum", pageNum + "")
					.meta("pageType", "searchEngine").meta("depth", "1");
			addSeed(datum);
		}
	}

	@Override
	public void visit(Page page, CrawlDatums next) {

		String keyword = page.meta("keyword");
		String pageType = page.meta("pageType");
		int depth = Integer.valueOf(page.meta("depth"));
		if (pageType.equals("searchEngine")) {
			int pageNum = Integer.valueOf(page.meta("pageNum"));
			System.out.println("成功抓取关键词" + keyword + "的第" + pageNum + "页搜索结果");
			Elements results = page.select("li.b_ans h2>a,li.b_algo h2>a");
			for (int rank = 0; rank < results.size(); rank++) {
				Element result = results.get(rank);

				/*
				 * 我们希望继续爬取每条搜索结果指向的网页，这里统称为外链。
				 * 我们希望在访问外链时仍然能够知道外链处于搜索引擎的第几页、第几条，
				 * 所以将页号和排序信息放入后续的CrawlDatum中，为了能够区分外链和
				 * 搜索引擎结果页面，我们将其pageType设置为outlink，这里的值完全由 用户定义，可以设置一个任意的值
				 * 
				 * 在经典爬虫中，每个网页都有一个refer信息，表示当前网页的链接来源。
				 * 例如我们首先访问新浪首页，然后从新浪首页中解析出了新的新闻链接，
				 * 则这些网页的refer值都是新浪首页。WebCollector不直接保存refer值，
				 * 但我们可以通过下面的方式，将refer信息保存在metaData中，达到同样的效果。
				 * 经典爬虫中锚文本的存储也可以通过下面方式实现。
				 * 
				 * 在一些需求中，希望得到当前页面在遍历树中的深度，利用metaData很容易实现
				 * 这个功能，在将CrawlDatum添加到next中时，将其depth设置为当前访问页面 的depth+1即可。
				 */
				CrawlDatum datum = new CrawlDatum(result.attr("abs:href"))
						.meta("keyword", keyword).meta("pageNum", pageNum + "")
						.meta("rank", rank + "").meta("pageType", "outlink")
						.meta("depth", (depth + 1) + "")
						.meta("refer", page.getUrl());
				next.add(datum);
			}

		} else if (pageType.equals("outlink")) {
			int pageNum = Integer.valueOf(page.meta("pageNum"));
			int rank = Integer.valueOf(page.meta("rank"));
			String refer = page.meta("refer");

			String line = String.format(
					"第%s页第%s个结果:%s(%s字节)\tdepth=%s\trefer=%s", pageNum,
					rank + 1, page.doc().title(), page.getContent().length,
					depth, refer);
			System.out.println(line);

		}
	}

	public static void main(String[] args) throws Exception {

		DemoBingCrawler crawler = new DemoBingCrawler("网络爬虫", 3);
		crawler.start();

	}

	/**
	 * 根据关键词和页号拼接Bing搜索对应的URL
	 */
	public static String createBingUrl(String keyword, int pageNum)
			throws Exception {
		int first = pageNum * 10 - 9;
		keyword = URLEncoder.encode(keyword, "utf-8");
		return String.format("http://cn.bing.com/search?q=%s&first=%s",
				keyword, first);
	}

}